﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NetOrientDB.ObjectModel;
using Newtonsoft.Json.Linq;

namespace NetOrientDB.HttpClient
{
	public class Connection : IDisposable
	{
		#region private vars
		private string server = string.Empty;
		private string database = string.Empty;
		private string username = string.Empty;
		private string password = string.Empty;
		private bool connected = false;
		private List<ObjectModel.OrientClass> classes = new List<ObjectModel.OrientClass>();
		private string sessionId = string.Empty;
		#endregion

		#region ctor
		public Connection(string server, string database, string username, string password)
		{
			// TODO: add validations

			// assign local variables
			this.server = server;
			this.username = username;
			this.password = password;
			this.database = database;
		}
		#endregion

		#region methods
		public void Connect()
		{
			if (this.connected) { return; }
			//
			string url = string.Format("{0}/connect/{1}", server, database);
			var response = HttpWrapper.SendRequest(url, "GET", "", this.username, this.password, this.sessionId);
			if (response.StatusCode == 0)
			{
				throw new GeneralException("Server not running.");
			}
			else if (response.StatusCode == 401)
			{
				throw new UnauthorizedAccessException("Invalid username or password");
			}
			else if (response.StatusCode != 200)
			{
				throw new Exception(string.Format("{0} {1}", response.StatusCode, response.StatusString));
			}
			//
			this.sessionId = response.OSessionId;
			var jObj = response.GetJson();
			var jClasses = jObj["classes"];
			foreach (var jClass in jClasses)
			{
				var cls = new ObjectModel.OrientClass((JObject)jClass);
				this.classes.Add(cls);
			}
			this.connected = true;
		}
		public void Disconnect()
		{
			if (!this.connected)
			{
				return;
			}
			string url = string.Format("{0}/disconnect", server);
			var response = HttpWrapper.SendRequest(url, "GET", "", this.username, this.password, this.sessionId);
			if (response.StatusCode == 200)
			{
				this.sessionId = string.Empty;
				this.connected = false;
			}
		}
		public OrientDocument GetDocumentByRid(string rid, string fetchPlan = "")
		{
			if (!this.connected)
			{
				throw new InvalidOperationException("Not connected. Please connected first.");
			}
			OrientDocument rv = null;
			//
			string url = string.Format("{0}/document/{1}/{2}", this.server, this.database, rid);
			if (!string.IsNullOrEmpty(fetchPlan))
			{
				url += "/" + fetchPlan;
			}
			var response = HttpWrapper.SendRequest(url, "GET", "", this.username, this.password, this.sessionId);
			if (response.StatusCode != 200)
			{
				return null;
			}
			var jObj = response.GetJson();
			rv = new OrientDocument();
			rv.Parse(jObj);
			//
			return rv;
		}
		public string InsertDocument(OrientDocument doc)
		{
			// TODO: Add validations
			if (!this.connected)
			{
				throw new InvalidOperationException("Not connected. Please connected first.");
			}

			string url = string.Format("{0}/document/{1}", this.server, this.database);
			string body = doc.GetJsonForInsert();
			var response = HttpWrapper.SendRequest(url, "POST", body, this.username, this.password, this.sessionId);
			if (response.StatusCode != 201)
			{
				string sExMessage = string.Format("{0} - {1}", response.StatusCode, response.StatusString);
				if (!string.IsNullOrEmpty(response.Body))
				{
					sExMessage = response.Body;
				}
				throw new Exception(sExMessage);
			}
			doc.Rid = response.Body;
			return doc.Rid;
		}
		public string UpdateDocument(OrientDocument doc)
		{
			// TODO: Add validations
			if (!this.connected)
			{
				throw new InvalidOperationException("Not connected. Please connected first.");
			}

			string url = string.Format("{0}/document/{1}/{2}", this.server, this.database, doc.Rid);
			string body = doc.GetJsonForUpdate();
			var response = HttpWrapper.SendRequest(url, "PUT", body, this.username, this.password, this.sessionId);
			if (response.StatusCode > 201)
			{
				string sExMessage = string.Format("{0} - {1}", response.StatusCode, response.StatusString);
				if (!string.IsNullOrEmpty(response.Body))
				{
					sExMessage = response.Body;
				}
				throw new Exception(sExMessage);
			}
			doc.Rid = response.Body;
			return doc.Rid;
		}
		public void DeleteDocument(string rid)
		{
			// TODO: Add validations
			string url = string.Format("{0}/document/{1}/{2}", this.server, this.database, rid);
			var response = HttpWrapper.SendRequest(url, "DELETE", "", this.username, this.password, this.sessionId);
			if (response.StatusCode != 204)
			{
				throw new RestException
				{
					StatusCode = response.StatusCode,
					Body = response.Body,
					StatusMessage = response.StatusString,
					Url = url
				};
			}
		}
		public List<OrientDocument> GetDocumentsOfClass(string className, int top = 20)
		{
			// TODO: Add Validations

			List<OrientDocument> rv = new List<OrientDocument>();
			string url = string.Format("{0}/class/{1}/{2}/{3}", this.server, this.database, className, top);
			var response = HttpWrapper.SendRequest(url, "GET", "", this.username, this.password, this.sessionId);
			if (response.StatusCode != 200)
			{
				throw new RestException
				{
					StatusCode = response.StatusCode,
					Body = response.Body,
					StatusMessage = response.StatusString,
					Url = url
				};
			}
			var jRoot = response.GetJson();
			var jResults = jRoot.Value<JArray>("result");
			foreach (var jResult in jResults)
			{
				var doc = new OrientDocument();
				doc.Parse((JObject)jResult);
				rv.Add(doc);
			}
			return rv;
		}
		public List<OrientDocument> GetDocumentsOfCluster(string clusterName, int top = 20)
		{
			// TODO: Add Validations

			List<OrientDocument> rv = new List<OrientDocument>();
			string url = string.Format("{0}/cluster/{1}/{2}/{3}", this.server, this.database, clusterName, top);
			var response = HttpWrapper.SendRequest(url, "GET", "", this.username, this.password, this.sessionId);
			if (response.StatusCode != 200)
			{
				throw new RestException
				{
					StatusCode = response.StatusCode,
					Body = response.Body,
					StatusMessage = response.StatusString,
					Url = url
				};
			}
			var jRoot = response.GetJson();
			var jResults = jRoot.Value<JArray>("result");
			foreach (var jResult in jResults)
			{
				var doc = new OrientDocument();
				doc.Parse((JObject)jResult);
				rv.Add(doc);
			}
			return rv;
		}
		public long GetDocumentCountOfClass(string className)
		{
			// TODO: Add validations

			long rv = 0;
			string url = string.Format("{0}/query/{1}/sql/select count(*) from {2}/-1", this.server, this.database, className);
			var response = HttpWrapper.SendRequest(url, "GET", string.Empty, this.username, this.password, this.sessionId);
			if (response.StatusCode != 200)
			{
				throw new RestException
				{
					StatusCode = response.StatusCode,
					Body = response.Body,
					StatusMessage = response.StatusString,
					Url = url
				};
			}
			var jRoot = response.GetJson();
			var jResults = jRoot.Value<JArray>("result");
			rv = jResults[0].Value<long>("count(*)");
			return rv;
		}
		public List<OrientDocument> ExecuteSqlQuery(string sqlQuery, int top = 20, string fetchPlan = "")
		{
			// TODO: Add validations
			List<OrientDocument> rv = new List<OrientDocument>();
			string url = string.Format("{0}/query/{1}/sql/{2}/{3}", this.server, this.database, sqlQuery, top);
			if (!string.IsNullOrEmpty(fetchPlan))
			{
				url += "/" + fetchPlan;
			}
			var response = HttpWrapper.SendRequest(url, "GET", string.Empty, this.username, this.password, this.sessionId);
			if (response.StatusCode != 200)
			{
				throw new RestException
				{
					StatusCode = response.StatusCode,
					Body = response.Body,
					StatusMessage = response.StatusString,
					Url = url
				};
			}
			var jRoot = response.GetJson();
			var jResults = jRoot.Value<JArray>("result");
			foreach (var jResult in jResults)
			{
				var doc = new OrientDocument();
				doc.Parse((JObject)jResult);
				rv.Add(doc);
			}
			return rv;
		}
		public long ExecuteSqlCommand(string sqlCommand)
		{
			// TODO: Add validations

			long rv = 0;
			string url = string.Format("{0}/command/{1}/sql", this.server, this.database);
			var response = HttpWrapper.SendRequest(url, "POST", sqlCommand, this.username, this.password, this.sessionId);
			if (response.StatusCode != 200)
			{
				throw new RestException
				{
					StatusCode = response.StatusCode,
					Body = response.Body,
					StatusMessage = response.StatusString,
					Url = url
				};
			}
			rv = Convert.ToInt64(response.Body);
			return rv;
		}
		#endregion

		#region properties
		public bool Connected
		{
			get
			{
				return this.connected;
			}
		}
		public List<OrientClass> Classes { get { return this.classes; } }
		#endregion

		#region IDisposable Implementation
		public void Dispose()
		{
			this.Disconnect();
		}
		#endregion


	}
}
